
local setmetatable
    = setmetatable
local strbyte, strchar
    = string.byte, string.char
local tconcat
    = table.concat
local floor
    = math.floor
local yield
    = coroutine.yield
local itercache = require 'dclm.cache.iterator'
local m = require 'lpeg'


module (...)


-- decode a two-byte UTF-8 sequence
local function f2(s)
  local c1, c2 = strbyte(s, 1, 2)
  return (c1 * 0x40) + c2 - 0x3080
end

-- decode a three-byte UTF-8 sequence
local function f3(s)
  local c1, c2, c3 = strbyte(s, 1, 3)
  return (c1 * 0x40 + c2) * 0x40 + c3 - 0xE2080
end

-- decode a four-byte UTF-8 sequence
local function f4(s)
  local c1, c2, c3, c4 = strbyte(s, 1, 4)
  return ((c1 * 0x40 + c2) * 0x40 + c3) * 0x40 + c4 - 0x3C82080
end

-- continuation byte
local cont = m.R'\128\191'

local P_char = m.R'\0\127'
               + (m.R'\194\223' * cont)
               + (m.R'\224\239' * cont * cont)
               + (m.R'\240\244' * cont * cont * cont)

local C_charcode = m.R'\0\127' / strbyte
                   + (m.R'\194\223' * cont) / f2
                   + (m.R'\224\239' * cont * cont) / f3
                   + (m.R'\240\244' * cont * cont * cont) / f4

local eachcharcode_patt = C_charcode * m.Cp()

local function eachcharcode_aux(str)
  local i = 1
  while true do
    local charcode, startnext = eachcharcode_patt:match(str, i)
    if (charcode == nil) then
      if (i <= #str) then
        error('invalid UTF-8 data at byte position: ' .. i)
      end
      return
    end
    yield(charcode)
    i = startnext
  end
end

function eachcharcode(str)
  return itercache.get(eachcharcode_aux, str)
end

function fromcharcode(c)
  if (c <= 0x7F) then
    return strchar(c)
  end
  if (c <= 0x7FF) then
    return strchar(
      0xC0 + floor(c / 0x40),
      0x80 + (c % 0x40))
  end
  if (c <= 0xFFFF) then
    return strchar(
      0xE0 + floor(c / 0x1000),
      0x80 + (floor(c / 0x40) % 0x40),
      0x80 + (c % 0x40))
  end
  return strchar(
    0xF0 + floor(c / 0x40000), 
    0x80 + (floor(c / 0x1000) % 0x40),
    0x80 + (floor(c / 0x40) % 0x40),
    0x80 + (c % 0x40))
end

function makestring(eachcharcode, ...)
  local t = {}
  for charcode in eachcharcode, ... do
    t[#t+1] = fromcharcode(charcode)
  end
  return tconcat(t)
end

